<?php
//确保在连接客户端时不会超时  
set_time_limit(0);

class WebSocket{
	public $sockets;//socket连接池
	public $users;  //所有socket连接进来的信息，包括socket,client名字等
	public $master; //socket的resource，即前期初始化socket时返回的socket资源

	public function __construct($address, $port){
		//创建socket并把保存socket资源在$this->master
		$this->master = $this->WebSocket($address, $port);
		//创建socket连接池
		$this->sockets = array($this->master);
	}

	private function WebSocket($address, $port){
		$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
		socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, TRUE);//1表示接受所有的数据包
		socket_bind($socket, $address, $port);
		socket_listen($socket);
		return $socket;
	}

	//对创建的socket循环进行监听，处理数据
	public function run(){
        $i = 0;
		while(true){
            echo "\n\n".++$i." time....................\n\n";
			$changes = $this->sockets;
			$write = NULL;
			$except = NULL;
			//为了阻塞程序继续往下执行,监听变化
			socket_select($changes,$write,$except,NULL);
			foreach($changes as $socket){
				//如果有新的client连接
				if($socket == $this->master){
					//接受一个新的socket连接
					$client = socket_accept($this->master);
					//给新连接一个唯一的id
					$key = uniqid();
					$this->sockets[] = $client;//把新的连接存放进连接池
					$this->users[$key] = array(
							'socket' => $client,//记录连接的sokcet信息
							'handshake' => false//标志握手动作没有完成
						);
				}else{//此处处理  1.client断开连接  2.client发送信息过来
					$len = 0;
					$buffer = '';
					do{
						//读取该socket信息
				 		$l=socket_recv($socket,$buf,1000,0);
				 		$len += $l;
				 		$buffer .= $buf;
					}while($len == 1000);
					//根据socket在user池子里面查找对应的$k,即唯一身份ID

                    $firstByte  = ord($buffer[0]);
                    $opcode     = $firstByte & 0xf;
                    echo "opcode:$opcode\n\n";
                    
					$key = $this->search($socket);
					//如果接收的信息长度小于7 ？ 还是 9 ？，则该client的socket为断开连接????
					if($len < 7){
                        echo "\n".$len."\n";
						//给该client的socket进行断开操作，并在$this->sockets和$this->users里面进行删除
						// $this->send2($k);
						$this->close($key);
						continue;
					}

					//判断该socket是否已经握手
                    //[如果浏览器发过来的是空内容怎么办？]//不接受或者判定为离线
                    if(!$this->users[$key]['handshake']){
                    	//如果没有握手，则进行握手处理
                    	$this->handshake($key, $buffer);
                    }else{
                    	//走到这里就是客户端发送消息给（其他客户端）端了
                        $buffer = $this->decode($buffer);

                    	if($buffer == false){
                    		continue;
                    	}
                    	//如果不为空，则进行消息推送操作
                    	$this->send($key,$buffer);
                    }
				}
			}
		}
	}
	
	//根据sock在users里面查找相应的$k
	private function search($socket){
		foreach($this->users as $k=>$v){
			if($socket == $v['socket']){
				return $k;
			}
		}
		return false;
	}

	//根据指定的key关闭socket
	private function close($key){
		//断开相应socket
        socket_close($this->users[$key]['socket']);
        //删除相应的user信息
        unset($this->users[$key]);
        //重新定义sockets连接池???
        $this->sockets=array($this->master);
        foreach($this->users as $v){
            $this->sockets[]=$v['socket'];
        }
        echo "===============\na socket closed\n===============\n";
        echo "A total of ".count($this->users)." users online\n";
        //有用户退出之后把消息推送给其他人

        $offline_msg = $this->encode(json_encode(
            array(
                'type'=>'offline',
                'key'=>$key,
                'online_nums'=>count($this->users),
                'msg'=>"用户".$key."退出了聊天室")
            )
        );
        foreach ($this->users as $user) {
            socket_write($user['socket'],$offline_msg,strlen($offline_msg));
        }
	}

	/*
    * 函数说明：对client的请求进行回应，即握手操作
    * @$k clien的socket对应的健，即每个用户有唯一$k并对应socket
    * @$buffer 接收client请求的所有信息
    */
    private function handshake($k, $buffer){
    	//截取Sec-WebSocket-Key的值并加密，其中$key后面的一部分258EAFA5-E914-47DA-95CA-C5AB0DC85B11字符串应该是固定的
    	$buf = substr($buffer,strpos($buffer,'Sec-WebSocket-Key:')+18);//what the meaning of this?
    	$key = trim(substr($buf,0,strpos($buf,"\r\n")));
    	$new_key = base64_encode(sha1($key."258EAFA5-E914-47DA-95CA-C5AB0DC85B11",true));

    	//按照协议组合信息进行返回
    	$new_message = "HTTP/1.1 101 Switching Protocols\r\n";
        $new_message .= "Upgrade: websocket\r\n";
        $new_message .= "Sec-WebSocket-Version: 13\r\n";
        $new_message .= "Connection: Upgrade\r\n";
        $new_message .= "Sec-WebSocket-Accept: " . $new_key . "\r\n\r\n";
        socket_write($this->users[$k]['socket'],$new_message,strlen($new_message));
        //对已经握手的client做标志
        $this->users[$k]['handshake'] = true;
        echo "A total of ".count($this->users)." users online\n";
        return true;
    }

    //向Client端推送消息
    private function send($key,$buffer){
        parse_str($buffer,$msg);
        // 在线人数
        $msg['online_nums'] = count($this->users);
        if(!array_key_exists('to', $msg)){
            // 走到这就说明浏览器异常关闭了
            // echo "jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj\n";
            // $str = socket_read($this->users[$key]['socket'], 1024);
            // socket_write($this->users[$key]['socket'],$str,strlen($str));
            $this->close($key);
        }else{
            if($msg['to']=='all'){
                //如果是上线消息的话
                if($msg['type'] == 'online'){
                    //将用户名和id一起绑定
                    $this->users[$key]['username'] = $msg['username'];
                    //传递一个key值
                    $msg['key'] = $key;
                }
                //给所有客户端发送消息(除了自己)
                $queue = $this->users;
                unset($queue[$key]);
                $str = $this->encode(json_encode($msg));
                foreach($queue as $user){
                    socket_write($user['socket'],$str,strlen($str));
                }
            }
            if($msg['to']=='one'){
                //向指定的socket发送消息
                $key = $msg['to'];
                $str = $this->encode($msg['msg']);
                socket_write($this->users[$key]['socket'],$str,strlen($str));
            }
        }
        
    }

    //数据解码
    function decode($received){
        $len = $masks = $data = $decoded = null;
        $buffer = $received;
        $len = ord($buffer[1]) & 127;
        if ($len === 126) {
           $masks = substr($buffer, 4, 4);
           $data  = substr($buffer, 8);
        } else {
           if ($len === 127) {
               $masks = substr($buffer, 10, 4);
               $data  = substr($buffer, 14);
           } else {
               $masks = substr($buffer, 2, 4);
               $data  = substr($buffer, 6);
           }
        }
        for ($index = 0; $index < strlen($data); $index++) {
           $decoded .= $data[$index] ^ $masks[$index % 4];
        }

        return $decoded;
    }

    //数据编码
    function encode($msg){
        $frame = array(); 
        $frame[0] = '81'; 
        $len = strlen($msg);
        if($len < 126){
            $frame[1] = $len<16?'0'.dechex($len):dechex($len);
        }else if($len < 65025){
            $s=dechex($len);
            $frame[1]='7e'.str_repeat('0',4-strlen($s)).$s;
        }else{
            $s=dechex($len);
            $frame[1]='7f'.str_repeat('0',16-strlen($s)).$s;
        }
        $frame[2] = $this->ord_hex($msg);
        $data = implode('',$frame); 
        return pack("H*", $data); 
    }

    function ord_hex($data)  { 
        $msg = ''; 
        $l = strlen($data); 
        for ($i= 0; $i<$l; $i++) { 
            $msg .= dechex(ord($data{$i})); 
        } 
        return $msg; 
    }
}
$WS = new WebSocket('192.168.100.142', 8890);
$WS->run();